/** GW Common.  This file holds various common routines to support GeoWiki.
*/
if( typeof console != 'undefined' ){ console.debug('base.js'); }

// These need to be defined in both config.py and in gwconfig.js
GW.TYPE_ICON = 'icon';                                                          // Feature types
GW.TYPE_POLYLINE = 'line';
GW.TYPE_POLYGON = 'shape';
GW.TYPE_KML = 'kml';

GW.POLY_WEIGHT = 2;                                                             // polyline and polygon defaults
GW.POLY_OPACITY = 0.6;
GW.POLY_FILLOPACITY = 0.2;
GW.POLY_EDIT_SIZE = '20px';                                                     // size the marker & color icons in edit.html
GW.ICON = 'reddot.png';                                                         // default marker icon
GW.maptypes = {
    Map: G_NORMAL_MAP,
    Satellite: G_SATELLITE_MAP,
    Hybrid: G_HYBRID_MAP,
    Terrain: G_PHYSICAL_MAP
};
GW.redirects = {                                                                // urls for standard pages
    defaultPage: 'Great_Loop_Home',                                             // DO NOT PROCEED WITH / since it is used as part of query
    map: '/gw/map',
    home: '/Great_Loop_Home',
    news: '/Great_Loop_News',
    help: '/Great_Loop_Help',
    faq: '/Great_Loop_FAQ',
    about: '/Great_Loop_About',
    feedback: '/Great_Loop_Feedback',
    termsOfUse: '/Great_Loop_Terms_of_use',
    privacy: '/Great_Loop_Privacy_policy',
    copyright: '/Great_Loop_Copyright',
    licenses: '/Great_Loop_Licenses',
    GWabout: '/GeoWiki_About',
    GWfeedback: '/GeoWiki_Feedback',
    GWtermsOfUse: '/GeoWiki_Terms_of_use',
    GWcopyright: '/GeoWiki_Copyright',
    GWlicenses: '/GeoWiki_Licenses'
};
GW.onLoad = function(){                                                         // onLoad
    GW.log.init();                                                              // initialize the log facility
    GW.menu.init();                                                             // init our menus
    GW.alert.init();                                                            // init alert facility
    GW.wait.init();                                                             // init wait panel
    GW.log.log( 'GW.onLoad' );
    GW.wait.show();
    if( GW.pageOnLoad ) { GW.pageOnLoad(); }                                    // if there is a pageOnUnload, execute it
    else {
        GW.wait.hide();
    }
};
GW.onUnload = function(){                                                       // onUnload
    GW.log.log( 'GW.onUnload' );
    if( GW.pageOnUnload ) { GW.pageOnUnload(); }                                // if there is a pageOnUnload, execute it
};
GW.log = {                                                                      // log support routines
    reader: null,
    visible: false,
    init: function(){                                                           // initialize the log reader
        GW.log.reader = new YAHOO.widget.LogReader( null, 
            {width: '450px', verboseOutput: false});
        GW.log.reader.hide();       // initially hidden
        GW.log.visible = false;
    },
    showHide: function(){                                                       // toggle reader visibility
        if( GW.log.visible ){
            GW.log.reader.hide();
            GW.log.visible = false;
        } else {
            GW.log.reader.show();
            GW.log.visible = true;
        }
    },
    log: function( msg, sev, comp ){                                            // output a log message
        sev = typeof(sev) != 'undefined'? sev: 'info';
        comp = typeof(comp) != 'undefined'? comp: 'GW';
        if( typeof console != 'undefined' ){ 
            switch( sev ) {
                case 'dbg':
                case 'debug':
                    console.debug( ':: ' + comp, arguments[0] ); 
                    break;
                case 'info':
                case 'log':
                    console.log( ':: ' + comp, arguments[0] ); 
                    break;
                default:
                    console.log( ':: ' + comp, arguments[0] ); 
                    break;
            }
        }
        YAHOO.log( ':: ' + msg, sev, comp);
    }
};
GW.menu = {                                                                     // menu support routines
    // NOTE: menu onclick handlers MUST be defined before GW.menu is processed!
    menu: null,                                                                 // the menu bar proper
    height: 0,                                                                  // height of menu bar
    data: [
        { text: 'user name'
        },
        { text: "Geowiki",                                                      // GewWiki navigation
            submenu: { id: 'idGeoWikiSubmenu',
                itemdata: [
                    { text: 'Map', url: GW.redirects.map },
                    { text: 'Home', url: GW.redirects.home },
                    { text: 'News', url: GW.redirects.news },
                    { text: 'Help', url: GW.redirects.help },
                    { text: 'FAQ', url: GW.redirects.faq },
                    { text: 'About', url: GW.redirects.about },
                    { text: 'Feedback', url: GW.redirects.feedback },
                    { text: 'More',
                        submenu: { id: 'idGreatLoopMoreSubmenu',
                            itemdata: [
                                { text: 'Terms of use', url: GW.redirects.termsOfUse },
                                { text: 'Privacy policy', url: GW.redirects.privacy },
                                { text: 'Copyright', url: GW.redirects.copyright },
                                { text: 'Licenses', url: GW.redirects.licenses }
                            ]
                        }
                    },
                    { text: 'GeoWiki',
                        submenu: { id: 'idGeoWikiMoreSubmenu',
                            itemdata: [
                                { text: 'About', url: GW.redirects.GWabout },
                                { text: 'Feedback', url: GW.redirects.GWfeedback },
                                { text: 'Terms of use', url: GW.redirects.termsOfUse },
                                { text: 'Copyright', url: GW.redirects.copyright },
                                { text: 'Licenses', url: GW.redirects.licenses }
                            ]
                        }
                    }
                ]
            }
        },
        { text: "Tools",                                                        // GewWiki tools
            submenu: { id: 'idEditSubmenu',
                itemdata: [
                    { text: 'Log', onclick: { fn: GW.log.showHide, scope: GW.log }}
                ]
            }
        },
        { text: "Admin",                                                        // Admin menus
            disabled: !GW.isAdmin,
            submenu: { id: 'idAdminSubmenu',
                itemdata: [
                    { text: 'Edit',
                        submenu: { id: 'idAdminEditSubmenu',
                            itemData: [
                                { text: 'Tags', url: '/gw/admin/tags' },
                                { text: 'Colors', url: '/gw/admin/colors' },
                                { text: 'Markers', url: '/gw/admin/markers' },
                                { text: 'Users', url: '/gw/admin/users' },
                                { text: 'Features', url: '/underconstruction/Pages' },
                                { text: 'Pages', url: '/underconstruction/Pages' }
                            ]
                        }
                    },
                    { text: 'Validate', url: '/underconstruction/Validate' },
                    { text: 'Developer',                                        // Developer menus
                        disabled: !GW.isDeveloper,
                        submenu: { id: 'idDeveloperSubmenu',
                            itemdata: [
                                { text: 'Erase/load data', url: '/gw/developer' }
                            ]
                        }
                    }
                ]
            }
        }
    ],
    disabled: function( item, val ){                                            // enable menu item
        var mm = YAHOO.widget.MenuManager.getMenuItem( item ).cfg.setProperty( "disabled", val );
    },
    onSubmenuShow: function(){                                                  // handle menu show issues for IE
        var oIFrame, 
            oElement,
            nOffsetWidth;
        if( YAHOO.env.ua.ie && ( this.id == 'idUserSubmenu' ||
                                 this.id == 'idGeoWikiSubmenu' || 
                                 this.id == 'idGewWikiMoreSubmenu' ||
                                 this.id == 'idEditSubmenu' ||
                                 this.id == 'idAdminSubmenu' ||
                                 this.id == 'idAdminEditSubmenu' ||
                                 this.id == 'idDeveloperSubmenu'
                                 )) {
            oElement = this.element;
            nOffsetWidth = oElement.offsetWidth;
            oElement.style.width = nOffsetWidth + "px"; 
            oElement.style.width = (nOffsetWidth - (oElement.offsetWidth - nOffsetWidth)) + "px";
        }
    },
    init: function(){                                                           // initialize our menus
        var mm = YAHOO.widget.MenuManager;
        if( GW.login ){                                                         // fix up user menu
            GW.menu.data[0] = { text: GW.nickname,
                                submenu: { id: 'idUserSubmenu',
                                    itemdata: [
                                        { text: 'Profile', url: '/underConstruction' },
                                        { text: 'Sign out', url: '/gw/logout' }
                                    ]
                                }
                            };
        } else {
            GW.menu.data[0] = { text: 'Sign in', url: '/gw/login' };
        }
        if( GW.menu.tools ){
            GW.menu.data[2] = GW.menu.tools;
        }
        GW.menu.menu = new YAHOO.widget.MenuBar('idMenuBar', {                  // build the menu
            lazyload: false,
            autosubmenudisplay: true,
            itemdata: GW.menu.data
            });
        GW.menu.menu.render( document.getElementById('idMenu') );
        GW.menu.menu.subscribe('show', this.onSubmenuShow );
        GW.menu.height = document.getElementById("idMenu").offsetHeight;        // get the menu since we need it's size
        // GW.log.log( 'menu init offset height ' + this.height );
    }
};
GW.busy = {                                                                     // busy gadget 
    count: 0,                                                                   // number of shows outstanding
    show: function( text ){                                                     // show wait gadget with text
        var d = document.getElementById( 'idLoadingDiv' );
        d.innerHTML = '<div style="position: relative; top: 5px; float:left; padding-right: 0.5em;"> '+ text + '</div><img src="/static/img/loading2.gif" style="width: 26px; height: 26px;">';
        YAHOO.util.Dom.setStyle( ['idLoadingDiv'], 'display', 'block' );
        GW.busy.count += 1;
    },
    hide: function( text ){                                                     // hide wait gadget
        GW.busy.count -= 1;
        var d = document.getElementById( 'idLoadingDiv' );
        if( GW.busy.count <= 0 ){
            GW.busy.count = 0;
            YAHOO.util.Dom.setStyle( ['idLoadingDiv'], 'display', 'none' );
        } else {
            d.innerHTML = '<img src="/static/img/loading2.gif" style="width: 26px; height: 26px;">';
        }
    }
};
GW.wait = {                                                                     // wait panel support
    panel: null,
    init: function(){
        GW.wait.panel = new YAHOO.widget.Panel( "idWait",                   // build wait panel
            { width: "240px", 
              fixedcenter: true, 
              close: false, 
              draggable: true, 
              zindex:9999,
              modal: true,
              visible: true
            } 
        );
        GW.wait.panel.setHeader( '' );
        GW.wait.panel.setBody('<img src="/static/img/loading.gif" />');
        GW.wait.panel.render(document.body);
        GW.log.log('show wait');
    },
    show: function( modal, header, xy ){                                        // display wait panel
        var m = typeof(modal) != 'undefined' ? modal : true;
        var h = typeof(header) != 'undefined' ? header : 'Loading, please wait ...';
        var xy = typeof(xy) != 'undefined' ? xy : '';
        GW.wait.panel.setHeader( h );
        GW.wait.panel.show();
        if( !m ){
            GW.wait.panel.hideMask();
        }
        if( xy != '' ){
            GW.wait.panel.moveTo( xy[0], xy[1] );
        }
        return;
    },
    hide: function(){
        GW.wait.panel.hide();
    }
};
GW.vpWidth = YAHOO.util.Dom.getViewportWidth;                                   // convenience method for screen width/height
GW.vpHeight = YAHOO.util.Dom.getViewportHeight;
GW.div = {                                                                      // div attribute support routines
    fullscreen: function( id ){                                                 // set div id id height to screen height less menu height and width to 100%
        var e = document.getElementById( id );
        e.style.height = GW.vpHeight() - GW.menu.height - 0 + 'px';
        e.style.width = '100%';
        GW.log.log( id + ' height ' + e.style.height + ' width 100%' );
    },
    fullheight: function( id ){                                                 // set div id height to screen height less menu height
        var e = document.getElementById( id );
        e.style.height = GW.vpHeight() - GW.menu.height - 0 + 'px';
        GW.log.log( id + ' height ' + e.style.height );
        return;
    },
    leftright: function( idLeft, idRight ){                                     // set width and height of left and right divs to split the screen
        var w = GW.vpWidth();
        GW.log.log( 'leftright vp width ' + w );
        var wl = parseInt( w/2, 10 );
        var wr = w - wl;
        GW.log.log( 'leftright wl ' + wl + ' wr ' + wr );
        var h = GW.vpHeight();
        GW.log.log( 'leftright vp height ' + h );
        var el = document.getElementById( idLeft ); 
        el.style.height = h - GW.menu.height - 0 + 'px';
        el.style.width = wl - 1 + 'px';                                         // at some browser widths it is necessary to be a bit less wide.  Don't know why!
        el.style.cssFloat = 'left';                                             // for FF
        el.style.styleFloat = 'left';                                           // for IE
        GW.log.log( 'leftright left ' + idLeft + ' height ' + el.style.height + ' width ' + el.style.width + ' float left' );
        var er = document.getElementById( idRight ); 
        er.style.height = h - GW.menu.height - 0 + 'px';
        er.style.width = wr + 'px';
        er.style.cssFloat = 'left';                                             // for FF
        er.style.styleFloat = 'left';                                           // for IE
        GW.log.log( 'leftright right ' + idRight + ' height ' + er.style.height + ' width ' + er.style.width + ' float left' );
    }
};
GW.alert = {                                                                    // alert panel facility
    dialog: null,
    questionDialog: null,
    init: function(){
        // create a simple dialog to use for our alerts
        GW.alert.dialog = new YAHOO.widget.SimpleDialog("idGWAlertDialog", 
            { width: "300px",
                fixedcenter: true,
                visible: false,
                draggable: true,
                close: true,
                text: "Text goes here.",
                modal: true,
                icon: YAHOO.widget.SimpleDialog.ICON_INFO,
                constraintoviewport: true,
                buttons: [{ text:"OK", handler:function(){this.hide();}, isDefault:true } ]
            } 
        );
        // Render the Dialog
        GW.alert.dialog.render("idPageWrapper");
        // Create a simple dialog to use for yes/no questions
        GW.alert.questionDialog = new YAHOO.widget.SimpleDialog("idGWQuestionDialog", 
            { width: "300px",
                fixedcenter: true,
                visible: false,
                draggable: true,
                close: true,
                text: "Text goes here.",
                modal: true,
                icon: YAHOO.widget.SimpleDialog.ICON_INFO,
                constraintoviewport: true,
                buttons: [{ text:"Yes", handler:GW.alertOK, isDefault:true },
                    { text:"No", handler:GW.alertOK, isDefault:false }
                ]
            } 
        );
        // Render the Dialog
        GW.alert.questionDialog.render("idPageWrapper");
    },
    alert: function( header, message ){                                         // display alert dialog
        if( header == '' ) { header = 'Geo Wiki'; }
        GW.alert.dialog.setHeader( header );
        // GW.alert.dialog.setBody( message );
        GW.alert.dialog.configText( 'text', [message], {} );
        GW.alert.dialog.show();
        GW.alert.dialog.bringToTop();
    },
    yesno: function( header, message, buttons ){                                // display yes/no dialog
        if( header == '' ) { header = 'Geo Wiki'; }
        GW.alert.questionDialog.setHeader( header );
        // GW.alert.dialog.setBody( message );
        GW.alert.questionDialog.configText( 'text', [message], {} );
        GW.alert.questionDialog.show();
        GW.alert.questionDialog.bringToTop();
    }
};
GW.dump = function( text, module, object ){                                     // Dump an object.  Useful during debugging.
    GW.console( 'Dumping object ' + text + ' from module ' + module, 'dump', module );
    var keys = [];
    for (var k in object ){
        keys.push(k);
    }
    keys.sort();
    for (var i in keys){
        var k = keys[i];
        var path = text + '.' + k;
        if( typeof object[k] == 'object' ){
            GW.dump( path, module, object[k] );
        } else {
            GW.console( ' ' +  path + '=' + object[k] + '     ', 'dump', module );
        }
    }
};
(function () {                                                                  // lat/lon map control class
    GW.latLonMapControl = function(){ };                                        // 
    GW.latLonMapControl.prototype = new GControl( true, true );                 // our GControl object
    GW.latLonMapControl.prototype.initialize = function( map ){                 // lat/lon initialization
        this.map = map;
        this.container = document.createElement( 'div' );
        this.container.style.width = '275px';
        this.container.style.backgroundColor = 'white';
        this.container.style.border = 'thin solid black';
        map.getContainer().appendChild( this.container );
        var ll = map.getCenter();
        this.container.innerHTML = '&nbsp;Mouse lat/lon&nbsp;&nbsp;Zoom';
        this.mMouseMove = function( mousePt ){
            this.container.innerHTML = '&nbsp;Lat: ' + mousePt.lat().toFixed(6) + ',&nbsp;Lon: ' + mousePt.lng().toFixed(6) + ',&nbsp;Zoom: ' + this.map.getZoom() + '&nbsp;';
        };
        var xThis = this;
        GEvent.addListener( this.map, 'mousemove', function( pt ){ xThis.mMouseMove(pt); });
        return this.container;
    };
    GW.latLonMapControl.prototype.getDefaultPosition = function(){              // lat/lon default positioning
        return new GControlPosition( G_ANCHOR_TOP_LEFT, new GSize( 70, 7 ));
    };
}());
(function () {                                                                  // map class
    GW.map = function( idDiv, enableEdit ){                                     // create a new map object
        GW.log.log( 'new map ' + idDiv );
        this.map = null;                                                        // our map object
        if (GBrowserIsCompatible()) {
            var e = document.getElementById( idDiv );
            this.map = new GMap2( e );                                          // create the map
            this.map.addMapType( G_PHYSICAL_MAP );
            this.map.addControl(new GOverviewMapControl(new GSize( 140, 140 )));
            this.map.addControl(new GLargeMapControl());
            this.map.addControl(new GMapTypeControl( true ));
            this.map.addControl(new GScaleControl(), new GControlPosition( G_ANCHOR_BOTTOM_LEFT, new GSize( 8, 35 )));
            this.map.enableGoogleBar();
            this.map.enableScrollWheelZoom();
            this.map.enableDoubleClickZoom();
            this.map.addControl( new GW.latLonMapControl( this.lat, this.lon, this.zoom ) );
            this.map.setMapType( GW.maptypes[GW.maptype] );
            var xthis = this;
            GEvent.addListener( this.map, 'zoomend', function(){xthis.moveZoom( xthis );} );
            GEvent.addListener( this.map, 'moveend', function(){xthis.moveZoom( xthis );} );
            GEvent.addListener( this.map, 'maptypechanged', function(){xthis.moveZoom( xthis );} );
            if( enableEdit ){                                                   // allow adding features
                GEvent.addListener( this.map, 'click', 
                    function( overlay, ll, overlayll ){ 
                        if( overlay ){ return; }                                    // click on an overlay object
                        xthis.addFeature( );
                    }
                );
            }
        } else {
            alert( 'Your browser is not compatible with Google maps.' );
        }
    };
    GW.map.prototype.setLLZT = function( lat, lon, zoom, type ){                // set the lat/lon/zoom/type
        var ll = new GLatLng( lat, lon );
        this.map.setCenter( ll, zoom, GW.maptypes[type]);
    };
    GW.map.prototype.setLLZTfromBB = function( bbN, bbS, bbE, bbW, type ){      // set the lat/lon/zoom from the bounding box
        lat = (bbN + bbS )/2;
        lon = (bbE + bbW )/2;
        zoom = this.map.getBoundsZoomLevel(
            new GLatLngBounds( new GLatLng( bbS, bbW ),
                               new GLatLng( bbN, bbE )));
        var ll = new GLatLng( lat, lon );
        this.map.setCenter( ll, zoom, GW.maptypes[type]);
    };
    GW.map.prototype.showFeatures = function( features, enableEdit ){           // show features on the map
        for ( var f in features ){
            var feature = features[f];
            this.showFeature( feature, enableEdit );
        }
        return;
    };
    GW.map.prototype.showFeature = function( feature, enableEdit ){             // show a single feature
        var xfeature;
        switch( feature.ftype ){
            case GW.TYPE_ICON:
                xfeature = this.createMarker( feature, enableEdit );
                break;
            case GW.TYPE_POLYLINE:
                xfeature = this.createLine( feature, enableEdit );
                break;
            case GW.TYPE_POLYGON:
                xfeature = this.createShape( feature, enableEdit );
                break;
            case GW.TYPE_KML:
                xfeature = this.createKML( feature, enableEdit );
                break;
        }
        feature.mapFeature = xfeature;                                          // remember the map feature
        GW.mapFeatureToFeature[ xfeature ] = feature;                           // index of map feature to feature
        GW.log.log( 'feature ' + feature.wikiTitle + 'with key ' + feature.key + ' placed on map');
    };
    GW.map.prototype.createMarker = function( feature, enableEdit ) {           // create a marker feature for the map
        var point = new GLatLng( feature.point[0], feature.point[1] );          // set our location
        var icon = new GIcon( G_DEFAULT_ICON );                                 // create our icon
        icon.image = "/gw/json/marker/" + feature.icon;
        icon.iconSize = new GSize( 32, 32 );
        icon.shadowSize=new GSize(56,32);
        icon.iconAnchor=new GPoint(16,32);
        icon.infoWindowAnchor=new GPoint(16,0);
        var markerOptions = { icon: icon, draggable: true,                      // create our marker
            autoPan: true, title: feature.name };
        var marker = new GMarker(point, markerOptions );
        this.map.addOverlay( marker );                                          // put it on the map
        this.updateExtra( feature, marker );
        if( !enableEdit ){
            GEvent.addListener(marker, "click", function( ll ) {                // if marker is clicked, ll is the click location, this is the marker/line/shape
                GW.displayFeaturePage.show( feature );
            });
            marker.disableDragging();
        }
        if( enableEdit ){                                                       // allow editing of feature on this map
            var xthis = this;
            GEvent.addListener(marker, "click", 
                function( ll ) {                                                // if marker is clicked, ll is the click location, this is the marker/line/shape
                    xthis.editFeature( feature, marker );
                }
            );
            GEvent.addListener(marker, "dragend", 
                function(ll) {                                                  // if marker is clicked, ll is the click location, this is the marker/line/shape
                    GW.dirty = true;
                    xthis.updateExtra( feature, marker );
                }
            );
        }
        return marker;
    };
    GW.map.prototype.setIcon = function( feature, icon ){                       // set the icon on an existing feature
        alert( 'set icon to ' + icon );
    };
    GW.map.prototype.createLine = function( feature, enableEdit ){              // create a line feature for our map
        points = [];
        for ( var i = 0; i < feature.points.length; i++ ){
            points[i] = new GLatLng( feature.points[i][0], feature.points[i][1] );
        }
        var line = new GPolyline( points, feature.color, feature.width, GW.POLY_OPACITY );
        this.map.addOverlay( line );                                            // put it on the map
        this.updateExtra( feature, line );
        if( !enableEdit ){
            GEvent.addListener(line, "click", function( ll, index ) {           // if line is clicked, ll is the click location, this is the marker/line/shape
                if (typeof index == "number") { return; }                       // ignour vertex clicks
                GW.displayFeaturePage.show( feature );
            });
        }
        if( enableEdit ) { this.createLineShape( line, feature ); }
        return line;
    };
    GW.map.prototype.createShape = function( feature, enableEdit ){             // create a shape feature for our map
        points = [];
        for ( var i = 0; i < feature.points.length; i++ ){
            points[i] = new GLatLng( feature.points[i][0], feature.points[i][1] );
        }
        var shape = new GPolygon( points, feature.color, feature.width,
            GW.POLY_OPACITY, feature.color, GW.POLY_FILLOPACITY );
        this.map.addOverlay( shape );                                           // put it on the map
        this.updateExtra( feature, shape );
        if( !enableEdit ){
            GEvent.addListener(shape, "click", function( ll ) {                 // if shape is clicked, ll is the click location, this is the marker/line/shape
                if (typeof index == "number") { return; }                       // ignour vertex clicks
                GW.displayFeaturePage.show( feature );
            });
        }
        if( enableEdit ) { this.createLineShape( shape, feature ); }
        return shape;
    };
    GW.map.prototype.createLineShape = function( lineshape, feature ) {         // new polyline/polygon initialization
        lineshape.enableEditing({onEvent: "mouseover"});                        // enable drawing and editing
        lineshape.disableEditing({onEvent: "mouseout"});
        var xthis = this;
        GEvent.bind(lineshape, "lineupdated", feature, function() {
            GW.dirty = true;
            xthis.updateExtra( feature, lineshape );
            return;
        });
        GEvent.addListener(lineshape, "click", function(latlng, index) {
            if (typeof index == "number") {
                lineshape.deleteVertex(index);
            } else {
                xthis.editFeature( feature, lineshape );
            }
            return;
        });
    };
    GW.map.prototype.createKML = function( feature, enableEdit ){               // create a KML feature
        var kml = new GGeoXml( feature.kml );
        if( kml ){
            this.map.addOverlay( kml );
            this.updateExtra( feature, kml );
            if( !nableEdit ){
                GEvent.addListener(kml, "click", function( ll ) {                   // if is clicked, ll is the click location, this is the marker/line/shape
                    GW.displayFeaturePage.show( feature );
                });
            }
        }
        return kml;
    };
    GW.map.prototype.getBounds = function( ){                                   // get current map bounds in geo coordinates
        return this.map.getBounds();
    };
    GW.map.prototype.moveZoom = function( xthis ){                              // update hash on move or zoom of map
        //GW.log.log( 'moveZoom event' );
        var center = xthis.map.getCenter();
        GW.lat = center.lat();
        GW.lon = center.lng();
        GW.zoom = xthis.map.getZoom();
        GW.maptype = xthis.map.getCurrentMapType().getName()
        GW.hash.set();
    };
    GW.map.prototype.updateExtra = function( feature, mls ){                    // compute feature extra
        switch( feature.ftype ){
            case GW.TYPE_ICON:
                var latlng = mls.getPoint();
                feature.extra = "(" + Math.round(latlng.y * 10000) / 10000 + ", " + Math.round(latlng.x * 10000) / 10000 + ")";
                break;
            case GW.TYPE_POLYLINE:
                var val = mls.getLength();
                feature.extra = (Math.round(val / 10) / 100) + "km " + 
                    (Math.round(val*0.0621371192)/100) + 'mi';
                break;
            case GW.TYPE_POLYGON:
                var area = mls.getArea();
                feature.extra = (Math.round(area / 10000) / 100) + "km<sup>2</sup> " +
                    (Math.round(0.386102159*area/10000)/ 100) + "mi<sup>2</sub>";
                break;
            case GW.TYPE_KML:
                feature.extra = 'Undefined';
                break;
            }
        return;
    };
    GW.map.prototype.editFeature = function( feature, mapfeature ){             // process edit feature, this is the GW map
        switch( feature.ftype ){
            case GW.TYPE_ICON:
                GW.marker.edit( feature, mapfeature );
                break;
            case GW.TYPE_POLYLINE:
                GW.line.edit( feature, mapfeature );
                break;
            case GW.TYPE_POLYGON:
                GW.shape.edit( feature, mapfeature );
                break;
            case GW.TYPE_KML:
                GW.kml.edit( feature, mapfeature );
                break;
        }
    };
    GW.map.prototype.addFeature = function( ){                                  // process add feature, this is the GW map
        GW.feature.showAddDialog();
    };
}());
GW.displayFeaturePage = {                                                       // map page display support, fixme: should be part of map class
    panel: null,                                                                // the panel
    feature: '',                                                                // currently displayed feature
    query: null,                                                                // query object
    init: function(){                                                           // init new page dialog
        var config = {                                                          // the PANEL configuration
            width: parseInt(GW.vpWidth() * 0.5, 10) + 'px',
            height: parseInt(GW.vpHeight() * 0.5, 10) + 'px',
            fixedcenter : true,
            visible : false, 
            constraintoviewport: true,
            modal: true
        };
        this.panel = new YAHOO.widget.Panel("idFeaturePageDisplayPanel", config );
        this.panel.render(document.body);
        this.query = new GW.query( 'pageDisplay' );
    },
    show: function( feature ){                                                  // show page display panel
        GW.displayFeaturePage.feature = feature;
        var wikiTitle = feature.wikiTitle;
        GW.displayFeaturePage.panel.bringToTop();
        if( GW.pages[ wikiTitle ] ){
            GW.displayFeaturePage.showContent();
        } else {
            var page = GW.displayFeaturePage.query.execute( GW.displayFeaturePage.showQueryDone, 
                'Loading page ' + wikiTitle,
                'page=' + wikiTitle + '&output=featurepages'
            );
        }
    },
    showQueryDone: function( o ){                                               // load the content
        GW.log.log( 'queryDone ' );
        if( o.status != 200 ){ return; }                                        // query failed
        var page = YAHOO.lang.JSON.parse( o.responseText)[0];
        GW.pages[ page.wikiTitle ] = page;
        GW.log.log( 'page featues ' + page.features );
        GW.displayFeaturePage.showContent();
    },
    showContent: function(){                                                    // show the page content
        var div = document.getElementById( 'idFeaturePageDisplayPanelBD' );
        var feature = GW.displayFeaturePage.feature;
        switch( feature.ftype ){
            case GW.TYPE_ICON:
                div.innerHTML = '<b>Location</b> ' + feature.extra + '<br /><hr width=100%" size="2" color="black" align="center">';
                break;
            case GW.TYPE_POLYLINE:
                div.innerHTML = '<b>Length</b> ' + feature.extra + '<br /><hr width=100%" size="2" color="black" align="center">';
                break;
            case GW.TYPE_POLYGON:
                div.innerHTML = '<b>Area</b> ' + feature.extra + '<br /><hr width=100%" size="2" color="black" align="center">';
                break;
            case GW.TYPE_KML:
                div.innerHTML = '';
                break;
        }
        div.innerHTML += GW.pages[ feature.wikiTitle ].content;
        GW.displayFeaturePage.panel.show();
    },
    showProperties: function(){                                                 // show the page properties, fixme: should use same as view
        var div = document.getElementById( 'idFeaturePageDisplayPanelBD' );
        var page = GW.pages[ GW.displayFeaturePage.feature.wikiTitle ];
        var xxx = '';
        xxx += '<div style="margin: 5px 5px 5px 5px;" >';
        xxx += '<table>';
        xxx += '<tr><td>Wiki Title</td><td>'+page.wikiTitle+'</td></tr>';
        xxx += '<tr><td>Title</td><td>'+page.title+'</td></tr>';
        xxx += '<tr><td>Revision/Current/Locked</td><td>'+page.revision+' / '+page.current+' / '+page.locked+'</td></tr>';
        xxx += '<tr><td>Author/Date</td><td>'+page.nickname+' / '+page.date+'</td></tr>';
        xxx += '<tr><td>Tags</td><td>'+ page.tags+'</td></tr>';
        xxx += '<tr><td>Features</td><td>';
        for( var f in page.features ){
            var feature = page.features[f];
            xxx += feature.ftype + ' ' + feature.extra + '<br />';
        }
        xxx += '</td></tr>';
        xxx += '<tr><td>Related feature pages</td><td>';
        for( var fp in page.featurepages){
            xxx += page.featurepages[fp] + '<br />';
        }
        xxx +='</td></tr>';
        xxx += '<tr><td>Related tag pages</td><td>';
        for( var fp in page.featuretags){
            xxx += page.featuretags[fp] + '<br />';
        }
        xxx +='</td></tr>';
        xxx += '<tr><td>Box (north, south, east, west)</td><td>'+
                page.bboxNorth +', ' + page.bboxSouth +', ' + 
                page.bboxEast +', ' + page.bboxWest +'</td></tr>';
        xxx += '</table>';
        xxx += '</div>';
        div.innerHTML = xxx;
    },
    page: function(){                                                           // display in page view
        window.location = "/" + GW.displayFeaturePage.feature.wikiTitle;
    },
    edit: function(){                                                           // edit the page
        window.location = "/gw/editpage/" + GW.displayFeaturePage.feature.wikiTitle;
    },
    hide: function(){                                                           // hide the page display
        GW.displayFeaturePage.panel.hide();
    }
};
(function () {                                                                  // query class
    GW.query = function( name ){                                                // query object
        this.callback = '';
        this.message = '';
        this.xquery = '';
        this.name = name;
    };
    GW.query.prototype.execute = function( callback, message, query ){          // execute a query and callback
        this.callback = callback;
        this.message = message;
        this.xquery = query;
        var reqCallback = {
            success: function(o){
                var msg = this.name + ' query succeeded. Status:' + o.status + ' Text:' + o.statusText;
                GW.log.log( msg, 'info', 'Query' );
                GW.busy.hide( message );
                this.callback( o );
            },
            failure: function(o){
                if( o.status !== -1 && o.status !== 0 ){                        // ignore transaction aborted failures and communication failures
                    var msg = this.name + ' query failed. Status:' + o.status + ' Text:' + o.statusText;
                    GW.log.log( msg, 'error', 'Query failed' );
                    alert( msg + ' Check log!' );
                }
                GW.busy.hide( message );
                this.callback( o );
            },
            scope: this,
            timeout: 40000
        };
        var url = '/gw/pageserver?' + query;
        GW.log.log( 'Query--->' + url, 'info', 'Query' );
        var request = YAHOO.util.Connect.asyncRequest( 'GET', url, reqCallback );
        GW.busy.show( message );
    };
    GW.query.box = function( map ){                                             // return map query box string
        var bnds = map.getBounds();
        var q = 'box=' + bnds.getNorthEast().lat() +
                       ',' + bnds.getSouthWest().lat() +
                       ',' + bnds.getNorthEast().lng() +
                       ',' + bnds.getSouthWest().lng();
        return q;
    };
    GW.query.scale = function( map ){                                           // return map scale for current box
        var bnds = map.getBounds();
        var q = 'scale=' + parseInt(bnds.getSouthWest().distanceFrom( bnds.getNorthEast() )*0.000621371, 10);
        return q;
    };
}());
GW.hash = {                                                                     // URL hash support
    lat: null,                                                                  // the current hash lat
    lon: null,
    zoom: null,
    maptype: null,
    havehash: function(){                                                       // true if we have a hash
        var h = window.location.hash;
        return (h != '' );
        },
    initLLZT: function( lat, lon, zoom, type ){                                 // process the URL hash
        GW.lat = GW.hash.getNumber( 'lat', lat ); 
        GW.lon = GW.hash.getNumber( 'lon', lon ); 
        GW.zoom = GW.hash.getNumber( 'zoom', zoom ); 
        GW.maptype = GW.hash.getText( 'maptype', type ); 
        return;
        },
    getNumber: function( key, xdef ){
        var a = '';
        a = GW.hash.get( key );
        if( a == '' ){
            return xdef;
        }
        a = parseFloat( a );
        if( isNaN( a )){
            return xdef;
        }
        return a;
    },
    getText: function( key, xdef ){
        var a = '';
        a = GW.hash.get( key );
        if( a == '' ){
            return xdef;
        }
        return a;
    },
    get: function( key ){
        var h = window.location.hash;
        if( h === '' ) { return ''; }
        var match = h.match(key+"=([^&]*)");
        if( match === null ) { return ''; }
        return match[1];
    },
    set: function( hash ){
        window.location.hash = 'lat=' + GW.lat + '&lon=' + GW.lon + 
            '&zoom=' + GW.zoom + '&maptype=' + GW.maptype;
    }
};
GW.serializeFeatureList = function( list, sep ){                                // serialize a list of feature wikiTitles to a string
    var s = sep || ', ';
    var v = '';
    if( list.length != 0 ){
        v = list[0].wikiTitle;
        for( var i=1; i < list.length; i++){
            v += s + list[i].wikiTitle;
        }
    }
    return v;
};
GW.serializeStringList = function( list, sep ){                                 // serialize an array of strings
    var s = sep || ', ';
    if( list.length == 0 ) { return ''; }
    var v = list[0];
    for( var i=1; i < list.length; i++ ){
        v += s + list[i];
    }
    return v;
};

if( typeof console != 'undefined' ){ console.debug('base.js'); }

(function () {
// Enclosing everything within this anonymous function
// makes everything declared inside invisible outside
// so I am free to declare any handy shortcuts as the ones below
// without messing up the global namespace.
})();